#include <stdlib.h>
#include <pthread.h>
#include "malloc.h"
#include "stack.h"
#include <stdint.h>
#include "non_blocking.h"

stackelem_t *
stackelem_alloc()
{
  return malloc(sizeof(stackelem_t));
}

int
stackelem_init(stackelem_t *elem)
{
  elem->data = malloc(sizeof(int));
  elem->next = NULL;
  return -1;
}

int
stackelem_free(stackelem_t *elem)
{
  free(elem);
  return -1;
}

int
stackelem_write(stackelem_t *elem, int data)
{
  *(elem->data) = data;
  return -1;
}

int
stackelem_read(stackelem_t *elem)
{
  if (elem == NULL) {
    return -1;
  }
  return *(elem->data);
}

stack_t *
stack_alloc()
{
  return (stack_t *) malloc(sizeof(stack_t));
}

int
stack_init(stack_t *stack) {
  stack->lock = malloc(sizeof(pthread_mutex_t));
  pthread_mutex_init(stack->lock, NULL);
  stack->top = NULL;
  return -1;
}

int
stack_free(stack_t *stack) {
  stackelem_t *tmp;
  if (stack != NULL) {
    while(stack->top != NULL) {
      tmp = stack->top; 
      stack->top = tmp->next;
      free(tmp);
    }
  } 
  return -1;
}

int
stack_isempty(stack_t *stack)
{
  return (stack == NULL);
}

int
stack_peek(stack_t *stack)
{
  if (stack != NULL) {
    return *((stack->top)->data);
  }
  return -1;
}

int
stack_push_elem(stack_t *stack, stackelem_t *elem)
{  
  stackelem_t *new_elem = stackelem_alloc();
  stackelem_init(new_elem);
  *(new_elem->data) = *(elem->data);
  new_elem->next = stack->top;
  stack->top = new_elem;
  return 1;
}

int
stack_pop_elem(stack_t *stack, stackelem_t **elem)
{
  if (stack->top != NULL) {
      stackelem_t *tmp = stack->top->next;
      *elem = stack->top;
      stack->top = tmp;
      return 1;
  }
  return 0;
}

int
stack_push_elem_safe(stack_t *stack, stackelem_t *elem)
{    
  pthread_mutex_lock(stack->lock);
  stackelem_t *new_elem = stackelem_alloc();
  new_elem->data = elem->data;
  new_elem->next = stack->top;
  stack->top = new_elem;
  pthread_mutex_unlock(stack->lock);
  return 1;
}

int
stack_pop_elem_safe(stack_t *stack, stackelem_t **elem)
{
  if (stack->top != NULL) {
    pthread_mutex_lock(stack->lock);
    stackelem_t *tmp = stack->top->next;
    *elem = stack->top;
    stack->top = tmp;
    pthread_mutex_unlock(stack->lock);
    return 1;
  }
  return 0;
}

int stack_init_treiber(stack_t *stack) {
  stack->lock = NULL;
  stack->top = NULL;
  return 1;
}

int stack_push_treiber(stack_t *stack, stackelem_t *elem) {
  stackelem_t *tmp_top;
  do {
    tmp_top = stack->top;
    elem->next = tmp_top;
  } while (!__sync_bool_compare_and_swap((uint32_t *) &stack->top, 
					(uint32_t *) tmp_top, 
					(uint32_t *) elem));
  return 1;
}

int stack_pop_treiber(stack_t *stack, stackelem_t **elem){
  stackelem_t *tmp_top;  
  stackelem_t *tmp_next;
  do {
    tmp_top = stack->top;
    tmp_next = stack->top->next;
    if (tmp_top == NULL) {
      //empty stack
      return 0;
    }
  } while (!__sync_bool_compare_and_swap((uint32_t *) &stack->top, 
					(uint32_t *) tmp_top, 
					(uint32_t *) tmp_next));
  tmp_top->next = NULL;
  *elem = tmp_top;
  return 1;
}

void print_stack(stackelem_t *elem, int depth) {
  printf("%d: %d\n", depth, *elem->data);
  if (elem->next != NULL) {
    print_stack(elem->next, depth+1);
  }
  
}
